// **************************************************************
// Com ++ Framework by the C++ World
// Please report defects: mailto:compp@cplusplusworld.com
// Please visit: http://www.cplusplusworld.com/socketlibrary.html
// **************************************************************

#include <iostream>
#include <memory>
#include <stdio.h>

//#include "config.h"
#include "SysPlusPlus/syscall.h"
#include "SysPlusPlus/syslib.h"
#include "SysPlusPlus/Tools.h"
#include "ComPlusPlus/Poll.h"
#include "ComPlusPlus/SocketUdp.h"
#include "SysPlusPlus/GenCfg.h"
#include "SysPlusPlus/ComException.h"


#define MAXUDP 65536

#include <sys/socket.h>

/**
 * Internal. Creates socket on OS level
 **/
void compp::SocketUdp::NewUdpSocket() {

    this->fd = syspp::Call::Socket ( PF_INET, SOCK_DGRAM, 0 );
    if ( this->fd == -1 ) {
      throw syspp::ComException("Panic: Cannot create socket!");
    }
    
   IsOpen = true;
   FdClosed = false;
}


/**
 *  Destructor. Shuts down the socket, if the socket was attached
 * with the AttachFD Method.
 */
compp::SocketUdp::~SocketUdp() {

  if ( this->fd == -1 )
  	return;	
  close ( this->fd );
}


/**
 *  Constructor.
 * Defaults are applied according to compp::GetComm 
 */
compp::SocketUdp::SocketUdp() {

  NewUdpSocket();

}

/**
 *  Creates a SocketUdp Class from an already established File Descriptor.
 * 
 * */
compp::SocketUdp::SocketUdp ( struct sockaddr_in *saddr, int len, int fd ) {

  this->fd = fd;
  this->IsOpen=true;	
  this->FdClosed = false;
}


/**
 * Establishes the class on host:port and applies 
 * defaults from compp::GenCfg
 * After that carries out bind 
 * Calling this constructor, one creates a working datagram socket already.
 */
compp::SocketUdp::SocketUdp(const std::string  & Addr, int port) {

  NewUdpSocket();
  this->Bind ( Addr, port );
}
/*
 * Establishes the class on 0:port and applies 
 * defaults from compp::GenCfg
 * After that carries out bind and listen.
 * Calling this constructor, one creates a working Udp socket already.
 */

compp::SocketUdp::SocketUdp( int port) {

  NewUdpSocket();
  this->Bind ( SocketANYHOST, port );

}


/**
 * Binds on string:port
 * 
 */
void compp::SocketUdp::Bind ( const std::string & addr, int port) {
  struct sockaddr_in ad;
 in_addr_t adv4;


  struct hostent *hent;

  (void) memset((void *) &adv4, 0, sizeof (adv4));

  if ( addr == SocketANYHOST ) {
    memset ( &  ad.sin_addr, 0, sizeof (ad.sin_addr));
  } else {
    if ( INADDR_NONE == ( adv4 = inet_addr ( addr.c_str() ))) {
      if ( this->GetUseDNS()  ) {
		if (NULL == (hent = syspp::Lib::Gethostbyname (addr.c_str() ))) {
		  //syspp::Logger *Log = syspp::Logger::Instance();
		  //Log->Error ( "Cannot Resolve Hostname" );
		  throw  syspp::ComException (  "compp::SocketUdp:Bind  Cannot Resolve Hostname " );
		} else {
		  std::memmove( &(adv4), *hent->h_addr_list , sizeof(adv4));
		  ad.sin_addr.s_addr = adv4; 
		}
      }
    } else {
      ad.sin_addr.s_addr = adv4;
    }
  }
  ad.sin_family = AF_INET;
  ad.sin_port   = htons(port);
  
#if 1
  if ( -1 == syspp::Call::Bind( this->fd, (struct sockaddr* )&ad, (socklen_t) sizeof(ad))) { 
#else
  if ( -1 == syspp::Call::Bind( this->fd, (struct sockaddr* )&ad, sizeof(ad))) { 
#endif

    //syspp::Logger *Log = syspp::Logger::Instance();
    //Log->Error ( "Cannot bind." );
    throw  syspp::ComException (  "compp::SocketUdp::Bind  Cannot Bind " );
  }
  this->SocketPort = port;
  State = Bound;
}

/**
 * Binds on 0:port
 */
void compp::SocketUdp::Bind ( const int port ) {
  this->Bind ( SocketANYHOST, port );
}

/**
 * Connect Socket to addr:port
*/
bool compp::SocketUdp::Connect( const std::string &addr, const int port ){
  if ( this->fd == -1 )
    NewUdpSocket();

   IsOpen = true;
   FdClosed = false;
 
 return _Connect( addr, port );
}

void compp::SocketUdp::SetSockOptBroadcast( bool yes ) { 
    int opt;

    if ( yes == true ) 
	opt = 1;
    else
	opt = 0;

#if 1
    if ( 0 != syspp::Call::Setsockopt (this->fd, SOL_SOCKET, SO_BROADCAST, (void*)&opt, (socklen_t)sizeof(opt) )) {
#else
    if ( 0 != syspp::Call::Setsockopt (this->fd, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt) )) {
#endif
		throw syspp::ComException("Cannot set option BROADCAST");
	}
}

/*
 * Joins Multicast Group. eg 224.1.1.1
 * Please see Example* Programs 
 **/
void compp::SocketUdp::SetSockOptJoinMulticastGroup ( const std::string & group ) {
#ifndef IP_ADD_MEMBERSHIP
  throw syspp::ComException ( "Cannot join multicast group. Multicasting not supported." );
#else
	struct ip_mreq mp;
    struct in_addr ad;
    
	_MkIPAddrIPV4(group, &ad);

	mp.imr_multiaddr = ad;

	_MkIPAddrIPV4("0.0.0.0", &ad);
	mp.imr_interface = (struct in_addr) ad;
	
	if ( -1 == syspp::Call::Setsockopt (this->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mp, sizeof(mp)) )
		throw syspp::ComException ( "Cannot join multicast group" );
#endif

}

/*
 * Withdraws from Multicast Group
 * Please see Example* Programs 
 **/
void compp::SocketUdp::SetSockOptWithdrawMulticastGroup ( const std::string & group ) {
#ifndef IP_DROP_MEMBERSHIP
 return ;
#else
	 
	struct ip_mreq mp;
    struct in_addr ad;
    
	_MkIPAddrIPV4(group, &ad);

	mp.imr_multiaddr = ad;
	_MkIPAddrIPV4("0.0.0.0", &ad);
	
	mp.imr_interface = ad;

	if ( -1 == syspp::Call::Setsockopt (this->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mp, sizeof(mp))) {
		throw syspp::ComException ( "Cannot leave multicast group" );	
	}
#endif	
}


/*
 * Sets loop value Multicast Group
 **/
void compp::SocketUdp::SetSockOptMulticastLoop ( bool val ) {
#ifndef IP_MULTICAST_LOOP
return ;
#else
	 
   int i = val ? 1 : 0;
   
	if ( -1 == syspp::Call::Setsockopt (this->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &i, sizeof(i))) {
		throw syspp::ComException ( "Cannot SetOpt Multicast Loopback " );	
	}
#endif
}

/*
 * Sets TTL value Multicast Group
 **/
void compp::SocketUdp::SetSockOptMulticastTTL ( int val ) {
#ifndef IP_MULTICAST_TTL
return ;
#else

   if ( val < 1 )
   	return ;	 
   
	if ( -1 == syspp::Call::Setsockopt (this->fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val))) {
		throw syspp::ComException ( "Cannot SetOpt Multicast TTL " );	
	}
#endif
}

int compp::SocketUdp::Write ( char *buf, int count ) {

  if ( count > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot write more than 65 K on UDP" );
  }

  int ret ;
  ret = this->Send(buf, count);
  return ret;

}


int compp::SocketUdp::Write ( const std::string & buf ) {    

  if ( buf.length() > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot write more than 65 K on UDP" );
  }

  int ret;
  ret = this->Send(buf.c_str(), buf.length());
  return  ret;  

}


int compp::SocketUdp::Writen ( const char *buf, int count ) {

  if ( count > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot write more than 65 K on UDP" );
  }

  int ret;
  ret = this->Send(buf, count );
  return ret;	
  
}


int compp::SocketUdp::Writen ( const std::string & buf ) {    

  if ( buf.length() > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot write more than 65 K on UDP" );
  }
  this->Send(buf.c_str() , buf.length() );
  return buf.length();
}

int compp::SocketUdp::Read ( char *buf, int count, std::string &host, int & port ) {    

  if ( count > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot read more than 65 K on UDP" );
  }

  if ( count <= 0 )
    return count;
  int retlen ;

  struct sockaddr_in from;
  int lenfrom= sizeof (struct sockaddr_in) ;

  retlen = syspp::Call::Recvfrom(fd, (void *) buf, count, 0, (struct sockaddr*) &from, &lenfrom);
  host= inet_ntoa ( (const in_addr &)  from.sin_addr.s_addr ) ;
  port = ntohs( from.sin_port);

  char ctmp[60];
  sprintf ( ctmp, "%u", port  );
  ContextString=host;
  ContextString+=":";
  ContextString+=ctmp;

  return retlen;
}

int compp::SocketUdp::Read ( std::string &buf, int count, std::string &host, int & port  ) {

  if ( count > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot read more than 65 K on UDP" );
  }

  if ( count <= 0 )
    return count;

  int retlen ;
  std::auto_ptr<char> c (new char[count+1]);

  struct sockaddr_in from;
  int len = sizeof( struct in_addr );
  
  retlen = syspp::Call::Recvfrom(fd, (void *) c.get(), count, 0, (struct sockaddr *) &from, &len);
  host= inet_ntoa ( (const in_addr &)  from.sin_addr.s_addr ) ;
  port = ntohs( from.sin_port);
  SocketAddress = host ;
  SocketPort = port;

  char ctmp[60];
  sprintf ( ctmp, "%u", port  );
  ContextString=host;
  ContextString+=":";
  ContextString+=ctmp;

  buf.assign ( (char*) c.get(), retlen);  

  return retlen;
}

int compp::SocketUdp::Read ( std::string &buf, std::string &host, int & port ) {    

  char c[ MAXUDP ];
  int retlen ;

  struct sockaddr_in from;
  int len = sizeof( from );

  retlen = syspp::Call::Recvfrom(fd, (void *) c, MaxNumBytesToRead, 0, (struct sockaddr *) &from, &len);
  host= inet_ntoa ( (const in_addr &)  from.sin_addr.s_addr ) ;
  SocketAddress = host ;

  port = ntohs( from.sin_port);
  SocketPort = port;
  buf.assign ( (char*) c, retlen);

  char ctmp[60];
  sprintf ( ctmp, "%u", port  );
  ContextString=host;
  ContextString+=":";
  ContextString+=ctmp;

  return retlen;
}


/**
 * Receives maximum len bytes and stores the in buf. Flags can be set.
 * Sender is stored in from/port. From is converted to a std::string
 * for convenience.
 */
int compp::SocketUdp::Recvfrom(void *buf, int len, int flags, std::string &from, int *fromport){

  if ( len > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot read more than 65 K on UDP" );
  }

	struct sockaddr_in fromsa;
	int ret;
	int i = sizeof ( fromsa);
	ret = syspp::Call::Recvfrom(this->fd, buf, len, flags, (struct sockaddr*) &fromsa, &i );
		
	*fromport = ntohs (fromsa.sin_port);
	char *c;
	c = inet_ntoa((in_addr) fromsa.sin_addr );
	from = c;
	
	if ( ret == -1  && errno != EAGAIN  ) {
		throw syspp::ComException( "compp::SocketUDP: Error in Recvfrom");
	}
	return ret;
}

/**
 * Sends len bytes to host/port.
 * Returns the number of bytes sent or throws ComException.
 * */
int compp::SocketUdp::Sendto(const void *buf, size_t len, const std::string &host, int port){     

  if ( len > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot write more than 65 K on UDP" );
  }

    struct sockaddr_in ad; 
    int ret;
    
	_MkSockaddrIPV4( host, (const int )port, &ad);

 	ret = syspp::Call::Sendto( this->fd, buf, len, 0,(const struct sockaddr *) &ad, (int) sizeof ( ad));
	if ( ret == -1 ) {
	  throw syspp::ComException( "compp::SocketUDP: Error in SendTo");
	}

	return ret;
}
/*
* High Performance variant of Sento
**/
int compp::SocketUdp::Sendto(const void *buf, size_t len, const struct sockaddr_in &ad ){

  if ( len > MAXUDP ) {
    throw syspp::ComException ( "compp::SocketUDP Cannot write more than 65 K on UDP" );
  }

  int ret = syspp::Call::Sendto( this->fd, buf, len, 0,(const struct sockaddr *) &ad, (int) sizeof ( ad));
  if ( ret == -1 ) {
    throw syspp::ComException( "compp::SocketUDP: Error in SendTo");
  }

  return ret;
}

int compp::SocketUdp::Readn ( char *buf, int count ) {

    std::string host;
    int port;
    return Read ( buf, count, host, port ) ;

}

int compp::SocketUdp::Readn ( std::string &buf, int count) {

    std::string host;
    int port;
    return Read ( buf, count, host ,port ) ;

}


